'use client'

import { useRef, useEffect, useState } from 'react'
import G6, { Graph } from '@antv/g6'
import userAlterIconSrc from '@/public/img/user.png'
import { Rect, Text, Circle, Image, Group, createNodeFromReact, appenAutoShapeListener } from '@antv/g6-react-node'
import { selectedDisputeThumbnailAtom } from '@/app/atoms'
import { useAtom } from 'jotai'
import { apiClient } from '@/lib/apiClient'
import { useParams, useRouter } from 'next/navigation'
import { arrayHelper } from '@/utils/common'
import { Spin } from 'antd'

const fittingString = (str, maxWidth, fontSize) => {
    const ellipsis = '...';
    const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
    let currentWidth = 0;
    let res = str;
    const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
    str.split('').forEach((letter, i) => {
      if (currentWidth > maxWidth - ellipsisLength) return;
      if (pattern.test(letter)) {
        // Chinese charactors
        currentWidth += fontSize;
      } else {
        // get the width of single letter according to the fontSize
        currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      if (currentWidth > maxWidth - ellipsisLength) {
        res = `${str.substr(0, i)}${ellipsis}`;
      }
    });
    return res;
}

function NodeContainer ({ cfg, ...rest }) {

    const { selected = false, avatarUrl } = cfg

    return (
        <Group>
            <Rect
                style={{
                    display: 'flex',
                    flexDirection: 'column',
                    cursor: 'pointer',
                    justifyContent: 'center',
                    alignContent: 'center',
                }}

            >
                <Rect
                    style={{
                        width: 36,
                        height: 45,
                        lineWidth: 1,
                        stroke: selected ? '#34b7b9' : '#d1d5db',
                        margin: [2, 'auto'],
                        radius: [1],
                        fill: selected ? '#34b7b9' : '#d1d5db',
                    }}
                >
                    <Image
                        alt='node'
                        class="rounded-md"
                        style={{
                            // img: "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*aHqIQIXL0RMAAAAAAAAAAABkARQnAQ",
                            img: avatarUrl ?? `https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*aHqIQIXL0RMAAAAAAAAAAABkARQnAQ`,
                            width: 34,
                            height: 43,
                            shadowColor: selected ? '#34b7b9' : '',
                            shadowBlur: selected ? 36 : 0,
                            margin: [1, 1],
                        }}
                        keyshape
                        draggable
                    />
                </Rect>
                <Text 
                    style={{ 
                        fill: '#000000A6', 
                        fontSize: 10,
                        margin: [2, 'auto'],
                    }}
                    keyshape
                    draggable
                >{cfg.label}</Text>
            </Rect>
        </Group>
    )
}

G6.registerNode('test', createNodeFromReact(NodeContainer))

G6.registerNode('inner-image', {
    draw(cfg, group) {
        const size = cfg.size
        const width = size[0] - 2
        const height = size[1] - 2

        const image = group.addShape('image', {
            attrs: {
              x: width / 2,
              y: height / 2,
              width: width,
              height: height,
              img: cfg.avatarUrl
            },
            // 在 G6 3.3 及之后的版本中，必须指定 name，可以是任意字符串，但需要在同一个自定义元素类型中保持唯一性
            name: 'image-shape'
        });

        return image
    }
}, 'rect')

const circleNodeConfig = {
    type: 'circle',
    style: { stroke: '#72CC4A' },
}

const imgNodeConfig = {
    type: 'cusImageNode',
    clipCfg: {
        show: true,
        type: 'circle',
        r: 24,
        width: 24,
        height: 24,
    },
}

export default function GraphContent ({
    selectedAssociationMethod,
    selectedLitigant,
    selectedResource,
    selectedCode,
    selectedType,
    selectedId,
    selectedOccurrenceTime,
    selectedGraphType,
    handleIsLoading,
}) {

    const params = useParams()
    const exploreMap = useRef(new Map())
    const selectedGraphTypeRef = useRef('')
    const router = useRouter()
    const explorePath = useRef([])

    const [selectedDisputeId, setSelectedDisputeId] = useAtom(selectedDisputeThumbnailAtom)
    const prevSelectedDisputeId = useRef()
    const containerRef = useRef()
    const graphRef = useRef()

    if (typeof selectedDisputeId === 'undefined') {
        if (graphRef.current && prevSelectedDisputeId.current) {
            const node = graphRef.current.findById(prevSelectedDisputeId.current?.id)
            if (node) {
                graphRef.current.updateItem(node, {
                    selected: false,
                })
                prevSelectedDisputeId.current = selectedDisputeId
            }
        }
    }

    if (selectedDisputeId?.id) {
        if (graphRef.current) {
            const node = graphRef.current.findById(selectedDisputeId?.id)
            if (node) {
                graphRef.current.updateItem(node, {
                    selected: true,
                })
                graphRef.current.getNodes().forEach((node) => {
                    graphRef.current.updateItem(node, {
                        selected: node._cfg.id === `${selectedDisputeId?.id}`,
                    })
                })
                prevSelectedDisputeId.current = selectedDisputeId
            }
        }
    }

    const getEdgeLabel = (id, nodes, source, target) => {

        if (target !== id) {
            return arrayHelper(nodes).find((node) => (node.id === target))?.relationList.join('、') ?? ''
        }

        if (source !== id) {
            return arrayHelper(nodes).find((node) => (node.id === source))?.relationList.join('、') ?? ''
        }

        return ''
    }

    const disputeExplorePeopleGraphQuey = async (id) => {
        try {

            handleIsLoading(true)
            const result = await apiClient.post(
                `graphAnalysis/exploreGraph/cp`,
                {
                    json: {
                        disputeId: id,
                    }
                },
            ).json()

            handleIsLoading(false)

            return result
        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    const peopleExploreDisputeQuery = async (id) => {
        try {
            handleIsLoading(true)
            const result = await apiClient.post(
                `graphAnalysis/exploreGraph/pc`,
                {
                    json: {
                        idCard: id,
                    }
                },
            ).json()

            handleIsLoading(false)

            return result
        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    const peopleExplorePeopleQuery = async (id) => {
        try {
            handleIsLoading(true)
            const result = await apiClient.post(
                `graphAnalysis/exploreGraph/pp`,
                {
                    json: {
                        idCard: id,
                    }
                },
            ).json()

            handleIsLoading(false)

            return result
        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    const generalExploreQuery = async (id, nodeType) => {
        try {
            handleIsLoading(true)
            const result = await apiClient.post(
                `graphAnalysis/exploreGraph/comprehensive`,
                {
                    json: {
                        id,
                        nodeType,
                    }
                },
            ).json()

            handleIsLoading(false)

            return result
        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    const trimAlreadyExistNode = (nodes) => {
        return arrayHelper(nodes).filter((node) => {
            let isExist = false
            exploreMap.current.forEach((_, key) => {
                const alreadyExistNode = exploreMap.current.get(key)
                arrayHelper(alreadyExistNode?.nodes).forEach((item) => {
                    if (item?.id === node?.id) {
                        isExist = true
                    }
                })
            })

            return !isExist
        })
    }

    const trimAlreadyExistEdge = (edges) => {
        return arrayHelper(edges).filter((edge) => {
            let isExist = false
            exploreMap.current.forEach((_, key) => {
                const alreadyExistNode = exploreMap.current.get(key)
                arrayHelper(alreadyExistNode?.edge).forEach((item) => {
                    if (`${item?.source}_${item?.target}` === `${edge?.source}_${edge?.targe}`) {
                        isExist = true
                    }
                })
            })

            return !isExist
        })
    }

    const formatExploreData = ({ id, nodes, edges,  preExploreNode }) => {
        const graphNodes = arrayHelper(nodes).map((item) => {
            return {
                id: item?.id,
                label: fittingString(item?.content, 48, 12),
                type: 'test',
                avatarUrl: item?.avatarUrl,
                selected: false,
                nodeType: item?.typeName,
                preExploreNode,
            }
        })

        const graphEdges = arrayHelper(edges).map((item) => {
            return {
                source: item?.srcNode,
                target: item?.dstNode,
                label: fittingString(`${getEdgeLabel(id, nodes, item?.srcNode, item?.dstNode)}`, 120, 12),
            }
        })

        return {
            nodes: graphNodes,
            edges: graphEdges,
        }
    }

    const removeItemHelper = (evt, id) => {
        if (graphRef.current) {
            const exploredData = exploreMap.current.get(id)
            arrayHelper(exploredData?.nodes).forEach((node) => {
                exploreMap.current.forEach((_, key) => {
                    if (id !== key) {
                        const prevExploredData = exploreMap.current.get(key)
                        arrayHelper(prevExploredData?.nodes).forEach((prevExploredNode) => {
                            if (node?.id !== prevExploredNode?.id) {
                                graphRef.current.removeItem(node?.id)
                            }
                        })
                    }
                })
            })

            const exploreEdges = evt.item._cfg.edges
            arrayHelper(exploreEdges).forEach((edge) => {

                const sourceID = edge.getSource()?._cfg.id
                const targetId = edge.getTarget()?._cfg.id

                const existEdge = arrayHelper(exploredData?.edges).find((item) => { return item?.source === sourceID && item?.target === targetId })

                exploreMap.current.forEach((_, key) => {
                    if (id !== key) {
                        const prevExploredData = exploreMap.current.get(key)
                        let isExitEdge = false
                        arrayHelper(prevExploredData?.edges).forEach((prevEdge) => {
                            if (`${prevEdge?.source}_${prevEdge?.target}` === `${sourceID}_${targetId}`) {
                                isExitEdge = true
                            }
                        })

                        if (!isExitEdge && existEdge) {
                            graphRef.current.removeItem(edge?._cfg?.id, false)
                        }
                    }
                })
            })

            exploreMap.current.delete(id)

            graphRef.current.layout()
            graphRef.current.render()
        }
    }

    const handleDbClick = async (evt) => {

        const id = evt.item._cfg.id

        if (graphRef.current) {

            if (exploreMap.current.has(id)) {

                if (id === params?.id) {
                    const exploredData = exploreMap.current.get(id)
                    explorePath.current = [id]

                    if (!exploredData?.dbClick) {
                        arrayHelper(exploredData?.nodes).forEach((node) => {
                            if (node?.id !== params?.id) {
                                graphRef.current.removeItem(node?.id)
                            }
                        })

                        exploreMap.current.set(id, { ...exploredData, dbClick: true })
                        graphRef.current.layout()
                    }

                    if (exploredData?.dbClick) {
                        arrayHelper(exploredData?.nodes).forEach((node) => {
                            if (node?.id !== params?.id) {
                                graphRef.current.addItem('node', node)
                            }
                        })
                        arrayHelper(exploredData?.edges).forEach((edge) => {
                            graphRef.current.addItem('edge', edge)
                        })

                        exploreMap.current.set(id, { ...exploredData, dbClick: false })
                        graphRef.current.layout()
                    }
        
                } else {
                    const exploredData = exploreMap.current.get(id)
                    const toBeDeleteNodeInExplorePathID = explorePath.current.findIndex((exploredID) => (exploredID === id))

                    const toBeDeleteExplorePath = explorePath.current.slice(toBeDeleteNodeInExplorePathID)

                    arrayHelper(toBeDeleteExplorePath).forEach((id) => {removeItemHelper(evt, id)})

                    explorePath.current = explorePath.current.slice(0, toBeDeleteNodeInExplorePathID)
                }
            } else if (!exploreMap.current.has(id)) {
                if (selectedGraphTypeRef.current === '1') {

                    if (evt.item._cfg.model?.nodeType === 'dispute') {
                        const result = await disputeExplorePeopleGraphQuey(id)
                        if (result.code === 200) {

                            const exploreData = formatExploreData({ id, nodes: result.data?.nodes, edges: result.data?.edges, preExploreNodeID: id })

                            const toBeAddNodes = trimAlreadyExistNode(exploreData?.nodes)
                            const toBeAddEdges = trimAlreadyExistEdge(exploreData?.edges)
                            exploreMap.current.set(id,  { edges: toBeAddEdges, nodes: toBeAddNodes })
                            explorePath.current.push(id)
                            arrayHelper(toBeAddNodes).forEach((node) => {
                                if (node?.id !== id) {
                                    graphRef.current.addItem('node', node)
                                }
                            })
                            arrayHelper(toBeAddEdges).forEach((edge) => {
                                graphRef.current.addItem('edge', edge)
                            })
    
                            graphRef.current.layout()
                            
                        }
                    }
    
                    if (evt.item._cfg.model?.nodeType === 'people') {
                        const result = await peopleExploreDisputeQuery(id)
                        if (result.code === 200) {
                            const exploreData = formatExploreData({ id, nodes: result.data?.nodes, edges: result.data?.edges,  preExploreNode: id })
                            const toBeAddNodes = trimAlreadyExistNode(exploreData?.nodes)
                            const toBeAddEdges = trimAlreadyExistEdge(exploreData?.edges)
                            exploreMap.current.set(id,  { edges: toBeAddEdges, nodes: toBeAddNodes })
                            explorePath.current.push(id)
                            arrayHelper(toBeAddNodes).forEach((node) => {
                                if (node?.id !== id) {
                                    graphRef.current.addItem('node', node)
                                }
                            })
                            arrayHelper(toBeAddEdges).forEach((edge) => {
                                graphRef.current.addItem('edge', edge)
                            })
    
                            // graphRef.current.fitView(2)
                            graphRef.current.layout()
                            
                        }
                    }
                }
    
                if (selectedGraphTypeRef.current === '2') {
                    if (evt.item._cfg.model?.nodeType === 'people') {
                        const result = await peopleExplorePeopleQuery(id)
                        if (result.code === 200) {
                            const exploreData = formatExploreData({ id, nodes: result.data?.nodes, edges: result.data?.edges, preExploreNode: id })
                            const toBeAddNodes = trimAlreadyExistNode(exploreData?.nodes)
                            const toBeAddEdges = trimAlreadyExistEdge(exploreData?.edges)
                            
                            exploreMap.current.set(id,  { edges: toBeAddEdges, nodes: toBeAddNodes })
                            explorePath.current.push(id)

                            arrayHelper(toBeAddNodes).forEach((node) => {
                                if (node?.id !== id) {
                                    graphRef.current.addItem('node', node)
                                }
                            })
                            arrayHelper(toBeAddEdges).forEach((edge) => {
                                graphRef.current.addItem('edge', edge)
                            })
    
                            // graphRef.current.fitView(2)
                            graphRef.current.layout()
                            
                        }
                    }
                }

                if (selectedGraphTypeRef.current === '3') {
                    const nodeType = evt.item._cfg.model?.nodeType
                    const result = await generalExploreQuery(id, nodeType)

                    if (result.code === 200) {
                        const exploreData = formatExploreData({ id, nodes: result.data?.nodes, edges: result.data?.edges, preExploreNode: id })
                        const toBeAddNodes = trimAlreadyExistNode(exploreData?.nodes)
                        const toBeAddEdges = trimAlreadyExistEdge(exploreData?.edges)
                        
                        exploreMap.current.set(id,  { edges: toBeAddEdges, nodes: toBeAddNodes })
                        explorePath.current.push(id)

                        arrayHelper(toBeAddNodes).forEach((node) => {
                            if (node?.id !== id) {
                                graphRef.current.addItem('node', node)
                            }
                        })
                        arrayHelper(toBeAddEdges).forEach((edge) => {
                            graphRef.current.addItem('edge', edge)
                        })

                        // graphRef.current.fitView(2)
                        graphRef.current.layout()
                    }
                }
            }
        }
    }

    useEffect(() => {
        if (graphRef.current || !containerRef.current) return

        const graph = new Graph({
            container: containerRef.current,
            // fitView: true,
            fitCenter: true,

            fitViewPadding: 2,
            modes: { 
                default: [
                    'drag-canvas', 
                    'zoom-canvas',
                    {
                        type: 'scroll-canvas',
                        enableOptimize: true,
                    },
                    'click-select',
                    'drag-node',
                ]
            },
            layout: { 
                type: 'force2',
                preventOverlap: true,
                nodeSize: 20,
            },
            defaultNode: {
                labelCfg: {
                    style: { fill: '#000000A6', fontSize: 10 },
                },
                size: 26,
                labelCfg: {
                    position: 'bottom'
                },
            },
            defaultEdge: {
                labelCfg: {
                    style: { fill: '#9a9a9a', fontSize: 10 },
                    autoRotate: true,
                },
                style: {
                    endArrow: {
                        path: G6.Arrow.vee(6, 9, 0),
                        d: 0,
                        fill: '#d1d5db',
                    },
                    startArrow: false,
                    stroke: '#d1d5db',
                }
            },
        })

        appenAutoShapeListener(graph)

        graph.on('node:click', (evt, node) => {
            const { item } = evt
            if (item._cfg?.id === params?.id) {
                setSelectedDisputeId(() => ({ id: item._cfg?.id, nodeType: 'people' }))
            } else {
                setSelectedDisputeId(() => ({ id: item._cfg?.id, nodeType: item._cfg.model?.nodeType }))
            }
        })

        graph.on('node:dblclick', handleDbClick)

        graph.on('node:contextmenu', (evt) => {
            if (evt.item._cfg.model?.nodeType === 'people') {
                router.push(`/association-graph/${evt.item._cfg.model?.id}?name=${evt.item._cfg.model?.label}&tab=profile`)
            }
        })

        graphRef.current = graph
    }, [])

    const initGraphData = ({ nodes, edges }) => {
        const graphNodes = arrayHelper(nodes).map((item) => {
            return {
                id: item?.id,
                label: fittingString(item?.content, 48, 12),
                type: 'test',
                avatarUrl: item?.avatarUrl,
                selected: false,
                nodeType: item?.typeName,
                preExploreNode: item?.id === params?.id ? undefined : params?.id,
            }
        })

        const graphEdges = arrayHelper(edges).map((item) => {
            return {
                source: item?.srcNode,
                target: item?.dstNode,
                label: fittingString(`${getEdgeLabel(params?.id, nodes, item?.srcNode, item?.dstNode)}`, 120, 12),
            }
        })

        if (exploreMap.current) {

            exploreMap.current.forEach((_, key) => {
                exploreMap.current.delete(key)
                explorePath.current = []
            })

            if (!exploreMap.current.has(params?.id)) {
                exploreMap.current.set(params?.id, {nodes: graphNodes, edges: graphEdges, dbClick: false })
                explorePath.current.push(params?.id)
            }
        }

        graphRef.current.read({ nodes: graphNodes, edges: graphEdges })
    }

    const people2thingsGraphQuery = async ({
        selectedAssociationMethod,
        selectedLitigant,
        selectedResource,
        selectedCode,
        selectedType,
        selectedId,
        selectedOccurrenceTime,
    }) => {
        try {
            handleIsLoading(true)
            const result = await apiClient.post(
                `graphAnalysis/exploreGraphWithCondition/pc`,
                {
                    json: {
                        idCard: params?.id,
                        associationTypes: selectedAssociationMethod,
                        disputePersonNames: selectedLitigant,
                        disputeSource: selectedResource,
                        disputeId: selectedCode,
                        disputePersonIdCard: selectedId,
                        disputeType: selectedType,
                        disputeOccurrenceTime: selectedOccurrenceTime,
                    }
                },
            ).json()

            handleIsLoading(false)

            if (result.code === 200) {

                initGraphData(result.data)
            }

            return result
        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    const people2PeopleGraphQuery = async ({
        selectedAssociationMethod,
        selectedLitigant,
        selectedResource,
        selectedCode,
        selectedType,
        selectedId,
        selectedOccurrenceTime,
    }) => {
        try {
            handleIsLoading(true)
            const result = await apiClient.post(
                `graphAnalysis/exploreGraphWithCondition/pp`,
                {
                    json: {
                        idCard: params?.id,
                        associationTypes: selectedAssociationMethod,
                        disputePersonNames: selectedLitigant,
                        disputeSource: selectedResource,
                        disputeId: selectedCode,
                        disputePersonIdCard: selectedId,
                        disputeType: selectedType,
                        disputeOccurrenceTime: selectedOccurrenceTime,
                    }
                },
            ).json()

            handleIsLoading(false)

            if (result.code === 200) {
                initGraphData(result.data)
            }

            return result
        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    const generalGraphQuery = async ({
        selectedAssociationMethod,
        selectedLitigant,
        selectedResource,
        selectedCode,
        selectedType,
        selectedId,
        selectedOccurrenceTime,
    }) => {
        try {
            handleIsLoading(true)

            const result = await apiClient.post(
                `graphAnalysis/exploreGraphWithCondition/comprehensive`,
                {
                    json: {
                        idCard: params?.id,
                        associationTypes: selectedAssociationMethod,
                        disputePersonNames: selectedLitigant,
                        disputeSource: selectedResource,
                        disputeId: selectedCode,
                        disputePersonIdCard: selectedId,
                        disputeType: selectedType,
                        disputeOccurrenceTime: selectedOccurrenceTime,
                    }
                },
            ).json()

            handleIsLoading(false)

            if (result.code === 200) {
                initGraphData(result.data)
            }

        } catch (error) {
            handleIsLoading(false)
            console.error(error)
            return error
        }
    }

    useEffect(() => {
        if (selectedGraphType === '1') {
            if (graphRef.current) {
                people2thingsGraphQuery({
                    selectedAssociationMethod,
                    selectedLitigant,
                    selectedResource,
                    selectedCode,
                    selectedType,
                    selectedId,
                    selectedOccurrenceTime,
                })
            }
        }

        if (selectedGraphType === '2') {
            if (graphRef.current) {
                people2PeopleGraphQuery({
                    selectedAssociationMethod,
                    selectedLitigant,
                    selectedResource,
                    selectedCode,
                    selectedType,
                    selectedId,
                    selectedOccurrenceTime,
                })
            }
        }

        if (selectedGraphType === '3') {
            if (graphRef.current) {
                generalGraphQuery({
                    selectedAssociationMethod,
                    selectedLitigant,
                    selectedResource,
                    selectedCode,
                    selectedType,
                    selectedId,
                    selectedOccurrenceTime,
                })
            }
        }
    }, [
        selectedAssociationMethod,
        selectedLitigant,
        selectedResource,
        selectedCode,
        selectedType,
        selectedId,
        selectedOccurrenceTime,
        selectedGraphType,
    ])

    useEffect(() => {
        selectedGraphTypeRef.current = selectedGraphType
    }, [selectedGraphType])

    return (
        <div
            ref={containerRef}
            style={{ 
                width: '100%', 
                height: '100%',
            }}
            onContextMenu={(e) => {e.preventDefault()}}
        />
    )
}